/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

//simple phong shader

struct PointLight
{
	float4 Position;
	float4 Color;
};

struct AreaLight
{
	float4 Position;
	float4 Color;
	float4 Corner;
	float4 V1;
	float4 V2;
};

cbuffer cbPerFrame	: register( b0 )
{
    matrix	View;
	matrix	Proj;
	matrix	ViewProj;
}

struct VS_Input
{
    float3 Position	: POSITION;
	float3 Normal	: NORMAL;
};

struct VS_Output
{
    float4 Pos		: SV_POSITION;
	float3 WPos		: TEXCOORD0;
	float3 Normal	: TEXCOORD1;
};


VS_Output VSMain( VS_Input Input )
{
    VS_Output Output;
    
    Output.Pos = mul( float4( Input.Position, 1 ), ViewProj );

	Output.WPos = Input.Position;
	Output.Normal = Input.Normal;
    
    return Output;
}

cbuffer cbPerObject		: register( b0 )
{
    float4 Diffuse;
	float4 Emission;
}

cbuffer cbLights		: register( b1 )
{
	AreaLight Light;
}

cbuffer cbShadowLight	: register( b2 )
{
    matrix	LightView;
	matrix	LightProj;
	matrix	LightViewProj;
}

struct VPL
{
	float4 pos;
	float4 color;
};

StructuredBuffer<VPL> VirtualPointLights		: register( t0 );
StructuredBuffer<VPL> VoxelVirtualPointLights	: register( t1 );

Texture2D		ShadowMap		: register( t2 );
SamplerState	ShadowSamp		: register( s0 );

#define USE_VOXELS 1
#define ENABLE_INDIRECT 1

#if USE_VOXELS
	#define VPLS VoxelVirtualPointLights
#else
	#define VPLS VirtualPointLights
#endif

#define PI 3.14159265358979323846f

static const float SHADOW_EPSILON = 0.005f;
static const float ShadowMapSize = 2048.0f;

float VSMShadow( float2 texcoord, float depth )
{
	float VSMEpsilon = 0.00001f;
	float ShadowEpsilon = 0.00001f;

	//variance shadow mapping
	float2 moments = ShadowMap.Sample( ShadowSamp, texcoord ).rg;			
			
	float lit_factor = (depth <= moments.x);
			
	float E_x2 = moments.y;
	float Ex_2 = moments.x * moments.x;
	float variance = min(max(E_x2 - Ex_2, VSMEpsilon) + ShadowEpsilon, 1.0);
	float m_d = (moments.x - depth);
	float p = variance / (variance + m_d * m_d); //Chebychev's inequality
			
	return max( lit_factor, p );
}

float4 PSMain( VS_Output Input ) : SV_TARGET
{
	float3 mtrlColor = Diffuse.xyz;
	float3 normal = normalize( Input.Normal );

	float3 lightVector = Light.Position.xyz - Input.WPos;
	float distSqrd = dot( lightVector, lightVector );

	lightVector = normalize( lightVector );

	float LnDl = saturate( dot( float3( 0.0f, 1.0f, 0.0f ), lightVector ) );
	float A = length( cross( Light.V1.xyz, Light.V2.xyz) );

	float attenuation = LnDl * A / (PI * distSqrd);

	//get shadow factor
	const float2 tcScale = float2( 0.5, -0.5 );
	float4 posLS = mul( float4( Input.WPos, 1.0f ), LightViewProj );

	float ndc = 1.0 / posLS.w;
	posLS.xyz *= ndc;
	posLS.xy =  tcScale * posLS.xy + 0.5;

	float shadow = VSMShadow( posLS.xy, posLS.z );

	float3 diffuse = shadow * attenuation * saturate( dot( normal, lightVector ) ) * Light.Color.xyz;

#if ENABLE_INDIRECT

	uint num, stride;
	VPLS.GetDimensions( num, stride );

	for( int i = 0; i < num; i++ )
	{
		VPL vpl = VPLS[ i ];

		lightVector = vpl.pos.xyz - Input.WPos;
		distSqrd = dot( lightVector, lightVector );
		attenuation = 4*PI / ( distSqrd + 32.0f );

		diffuse += attenuation * saturate( dot( normal, normalize( lightVector ) ) ) * vpl.color.xyz;
	}

#endif

	return float4( mtrlColor * diffuse, 1.0f );
}